#include "JpegFile.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <securec.h>
#include <iostream>
#include <fstream>

#include "opencv2/imgcodecs/legacy/constants_c.h"
#include <opencv2/imgproc.hpp>
#include "spdlog/stopwatch.h"

#include "hw_app_log.h"
#include "sdc_def_ext.h"

static int JPEGToMat(cv::Mat &matImage, std::vector<unsigned char> buff)
{
	if (buff.empty()) {
		return -1;
	}
	if ((buff[0] == 0xFF && buff[1] == 0xD8))
	{
		matImage = cv::imdecode(buff, CV_LOAD_IMAGE_COLOR);
	}
	else
	{
		buff.insert(buff.begin(), 0xFF);
		buff.insert(buff.begin()+1, 0xD8);
		matImage = cv::imdecode(buff, CV_LOAD_IMAGE_COLOR);
	}
	return 0;
}

/**
 * @brief 推理前的预处理，预警框外置黑（没有使用，耗时太长）
 * 
 * @param img 
 * @return cv::Mat 
 */
// static cv::Mat pre_process_Mat(const cv::Mat &img)
// {
// 	if (gc_app_config.camera_config_map[camera_key].alarm_roi.size() == 0) {
// 		return img;
// 	}
// 	cv::Mat dst = cv::Mat::zeros(img.size(), img.type());
// 	cv::Mat mask = cv::Mat::zeros(img.size(), img.type());
// 	std::vector<cv::Point2i> points;
// 	for (auto &berth: gc_app_config.camera_config_map[camera_key].alarm_roi) {
// 		if (berth.roi.size() < 3) {
// 			continue;
// 		}
// 		points.clear();
// 		for (int i = 0; i < berth.roi.size(); i += 2) {
// 			points.emplace_back((int) (berth.roi[i]), (int) (berth.roi[i + 1]));
// 		}
// 		cv::fillPoly(mask, std::vector<std::vector<cv::Point2i>>{points}, cv::Scalar(255, 255, 255));
// 	}
// 	cv::bitwise_and(img, mask, dst);
// 	// img.copyTo(dst, mask);
// 	cv::imwrite("/tmp/.AIPlus/mask.jpg", dst);
// 	return dst;
// }

static void pre_process_letterbox_Mat(const cv::Mat &img, int forwardsize_w, int forwardsize_h,cv::Mat &outputMat)
{
    spdlog::stopwatch sw;

	outputMat = cv::Mat::zeros( cv::Size(forwardsize_w, forwardsize_h),CV_32F);

	logger->debug("pre_process_letterbox_zeros_time: {:.3} s", sw);

	float w_ratio = 1.0f * outputMat.cols / img.cols;
	float h_ratio = 1.0f * outputMat.rows / img.rows;
	cv::Mat img_resize;
	float min_ratio = w_ratio > h_ratio ? h_ratio : w_ratio;
	int new_w = (int)(img.cols* min_ratio);
	int new_h = (int)(img.rows* min_ratio);

	sw.reset();

	cv::resize(img, img_resize, cv::Size(new_w, new_h), 0.0, 0.0, cv::INTER_NEAREST);

	logger->debug("pre_process_letterbox_resize_time: {:.3} s", sw);

	sw.reset();

	cv::copyMakeBorder(img_resize, outputMat, 0, outputMat.rows-img_resize.rows, 0, outputMat.cols-img_resize.cols,cv::BORDER_CONSTANT);

	logger->debug("pre_process_letterbox_border_time: {:.3} s", sw);
}

static void BGR2yuv_nv21(cv::Mat& img, unsigned char * pyuv){
	int imageLength = img.cols * img.rows * 3 / 2; 
    int wh =  img.cols * img.rows;
	int w = img.cols / 2 * 2; // bgr 转yuv时，需要宽高是偶数
	int h = img.rows / 2 * 2;
	
    int uv_len = w * h / 2;
	unsigned char *yuv = new unsigned char[uv_len];             /* 存储CV_BGR2YUV_I420数据 */
	cv::Mat out;
	out.create(h * 3 / 2 , w, CV_8UC2);

    cv::cvtColor(img, out, cv::COLOR_BGR2YUV_IYUV);                 /* BGR空间转换为CV_BGR2YUV_I420 */
	memcpy(pyuv, out.data, wh * sizeof(unsigned char));               /* 此时yuv_nv21中存储的是CV_BGR2YUV_I420类型数据 */
	memcpy(yuv, out.data + wh, uv_len*sizeof(unsigned char));         /* 此时yuv中存储的是CV_BGR2YUV_I420类型数据 */
	int num = 0;  /* 对u、v个数的计数 */
	for (int j = w * h; j != imageLength; j += 2) {
		pyuv[j + 1] = yuv[num];                          /* 对yuv_nv21中u分量进行赋值 */
		pyuv[j] = yuv[w * h / 4 + num];                 /* 对yuv_nv21中v分量进行赋值 */
		++num;
	}
	delete yuv;
}

namespace gc
{
JpegFile::JpegFile(void) : m_codecFd(-1)
{
	LOG_DEBUG("Create the Jpeg file object");
}

JpegFile::~JpegFile(void)
{
	if (m_codecFd != -1) {
		close(m_codecFd);
		m_codecFd = -1;
	}
	
	LOG_DEBUG("Destroy the Jpeg file object");
}

int32_t JpegFile::Init(void)
{
	// m_codecFd = open("/mnt/srvfs/codec.iaas.sdc", O_RDWR);
	// if (m_codecFd < 0) {
	// 	LOG_ERROR("Open the /mnt/srvfs/codec.iaas.sdc failed, errno: %d, errmsg: %s", errno, strerror(errno));
	// 	return ERR;
	// }
	// LOG_DEBUG("Open the /mnt/srvfs/codec.iaas.sdc successfully");
	
	// return HW_OK;
	return fd_codec < 0 ? ERR : HW_OK;
}

int32_t JpegFile::InitOsdRegion(sdc_yuv_frame_s &yuv_frame, sdc_osd_region_s &osd_region) const
{
	// 中文采用双字节编码
	// wchar_t Font[] = {
	// 	0x4e2d, 0x534e, 0x4eba, 0x6c11, 0x5171, 0x5548c, 0x56fd, 0xff0c, 0x6d59, 0x6c5f,   
	// 	0x7701, 0x676d, 0x5dde, 0x5e02, 0x6ee8, 0x6c5f, 0x533a, 0xff0c, 0x6d4b, 0x8bd5, 0xff01
    // };

	wchar_t Font[] = L"中文测试 BY cc!";
	// std::string str = "中文测试 BY cc!";
	// wchar_t Font[str.size()*2];
	// ToWchar((char *)str.c_str(),Font);

	memset_s(&osd_region, sizeof(osd_region), 0, sizeof(osd_region));
    osd_region.region.x = 0;
    osd_region.region.y = 0;
    osd_region.region.w = yuv_frame.width;
    osd_region.region.h = yuv_frame.height;
	(void)memcpy_s(osd_region.osd.format,sizeof(osd_region.osd.format),
		"fontsize=2;fgcolor=0x111111;fgalpha=120", sizeof("fontsize=2;fgcolor=0x111111;fgalpha=120") - 1);

    (void)memcpy_s(osd_region.osd.content, sizeof(osd_region.osd.content), Font, sizeof(Font));
    osd_region.osd.content_len = sizeof(Font);
	return HW_OK;
}

int32_t JpegFile::Yuv2Jpeg(sdc_yuv_frame_s &yuv_frame, sdc_osd_region_s &osd_region, sdc_jpeg_frame_s &jpeg_frame) const
{
	sdc_encode_jpeg_param_s param;
	memset_s(&param, sizeof(param), 0, sizeof(param));
	param.qf = 90; // TODO 使用工具相机配置
	param.osd_region_cnt = 1;
	param.region = { 0, 0, yuv_frame.width, yuv_frame.height };
	param.frame = yuv_frame;

	sdc_common_head_s head;
	memset_s(&head, sizeof(head), 0, sizeof(head));
	head.version = SDC_VERSION; // 0x5331
	head.url = SDC_URL_ENCODED_JPEG; // 0x00
	head.method = SDC_METHOD_CREATE;
	head.head_length = sizeof(head);
	head.content_length = sizeof(param) + sizeof(osd_region);
	// std::cout << sizeof(param) << " " <<  sizeof(osd_region) << " " << head.content_length << " " << sizeof(jpeg_frame) << "\n";
	struct iovec iov[3];
	iov[0].iov_base = &head;
	iov[0].iov_len = sizeof(head);
	iov[1].iov_base = &param;
	iov[1].iov_len = sizeof(param);
	iov[2].iov_base = (void *)&osd_region;
	iov[2].iov_len = sizeof(osd_region);

	int32_t nret = writev(fd_codec, iov, 3);
	if(nret < 0) {
		LOG_ERROR("Write the iovec failed, errno: %d, errmsg: %s", errno, strerror(errno));
		return errno;
	}

	iov[1].iov_base = &jpeg_frame;
	iov[1].iov_len = sizeof(jpeg_frame);
	nret = readv(fd_codec, iov, 2);
	if(nret < 0) {
		LOG_ERROR("Read the jpeg frame failed, errno: %d, errmsg: %s", errno, strerror(errno));
		return errno;
	}

	// std::cout << head.head_length << " " << sizeof(head) << " " << head.content_length << " " << sizeof(jpeg_frame) << "\n";


	if(head.head_length != sizeof(head) || head.content_length != sizeof(jpeg_frame)) {
		LOG_ERROR("Translate the Yuv frame to jpeg frame failed");
		return EIO;
	}
	LOG_DEBUG("Translate the Yuv frame to jpeg frame successfully");
	return HW_OK;
}

int32_t JpegFile::FreeJpeg(sdc_jpeg_frame_s &jpeg_frame) const
{
	sdc_common_head_s head;
	memset_s(&head, sizeof(head), 0, sizeof(head));
	head.version = SDC_VERSION; // 0x5331
	head.url = SDC_URL_ENCODED_JPEG; // 0x00
	head.method = SDC_METHOD_DELETE;
	head.head_length = sizeof(head);
	head.content_length = sizeof(jpeg_frame);

	struct iovec iov[2];
	iov[0].iov_base = (void *)&head;
	iov[0].iov_len = sizeof(head);
	iov[1].iov_base = (void *)&jpeg_frame;
	iov[1].iov_len = sizeof(jpeg_frame);
	
	int32_t nret = writev(fd_codec, iov, 2);
	if (nret < 0) {
		LOG_ERROR("Write the iovec failed, errno: %d, errmsg: %s", errno, strerror(errno));
	}
	LOG_DEBUG("Write the iovec successfully");

	return HW_OK;
}

int32_t JpegFile::SaveJpeg(sdc_yuv_frame_s &yuv_frame, string &jpegPath) const
{
	sdc_osd_region_s osd_region;
	memset_s(&osd_region, sizeof(osd_region), 0, sizeof(osd_region));
	
	// 如果需要添加1个OSD，请初始化InitOsdRegion()，否在注释OSD
	// (void) InitOsdRegion(yuv_frame, osd_region);

	// 将Yuv转换成图片帧
	sdc_jpeg_frame_s jpeg_frame;
	if (Yuv2Jpeg(yuv_frame, osd_region, jpeg_frame) != HW_OK) {
		LOG_ERROR("Translate Yuv to Jpeg failed");
		return ERR;
	}
	LOG_DEBUG("Translate Yuv to Jpeg successfully");

	// 用于测试，保存Jpeg图片帧至本地，如果客户将jpeg_frame上传至平台，不需要保存本地,
	// 只需要将jpeg_frame.addr_virt, jpeg_frame.size的内容封装成TLV数据内容，并上报至平台；
	(void) SaveJpeg(jpeg_frame, jpegPath);
	

	// 释放Jpeg图片帧
	(void) FreeJpeg(jpeg_frame);

	return HW_OK;
}

int32_t JpegFile::SaveJpeg(sdc_jpeg_frame_s &jpeg_frame, string &jpegPath) const
{
	int32_t fd = -1;
	if ((fd=open(jpegPath.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
		LOG_ERROR("Open the jpeg file failed, file_path: %s, errno: %d, errmsg: %s", jpegPath.c_str(), errno, strerror(errno));
		return ERR;
	}

	ssize_t writeLen= 0;
	if ((writeLen=write(fd, (void*)jpeg_frame.addr_virt, jpeg_frame.size)) < 0) {
		LOG_ERROR("Write the jpeg file failed, errno: %d, errmsg: %s", errno, strerror(errno));
	} else if(writeLen != (ssize_t)jpeg_frame.size) {
		LOG_ERROR("Write the jpeg file truncted, write_size: %ld, real_size: %u", writeLen, jpeg_frame.size);
	}else {
		LOG_DEBUG("Save the jpeg file successfully, file_path: %s, real_size: %u", jpegPath.c_str(), jpeg_frame.size);
	}
	close(fd);

	return HW_OK;
}

int32_t JpegFile::SaveJpeg(sdc_yuv_frame_s &yuv_frame, int forwardsize_w, int forwardsize_h, unsigned char * pyuv, cv::Mat &dst, std::string &save_path) const
{
	sdc_osd_region_s osd_region;
	// memset_s(&osd_region, sizeof(osd_region), 0, sizeof(osd_region));
	
	// 如果需要添加1个OSD，请初始化InitOsdRegion()，否在注释OSD
	// (void) InitOsdRegion(yuv_frame, osd_region);

    spdlog::stopwatch sw;

	// std::ofstream fs("test.yuv", std::fstream::binary);
	// fs.write((const char*)yuv_frame.addr_virt, yuv_frame.size);
	// fs.close();

	// sw.reset();

	// if (YuvToMat(yuv_frame, dst) != HW_OK) {
	// 	LOG_ERROR("YuvToMat failed");
	// 	return ERR;
	// }

	// logger->debug("YuvToMat_time: {:.3} s", sw);	

	sw.reset();
	// 将Yuv转换成图片帧
	sdc_jpeg_frame_s jpeg_frame;
	if (Yuv2Jpeg(yuv_frame, osd_region, jpeg_frame) != HW_OK) {
		LOG_ERROR("Translate Yuv to Jpeg failed");
		// 释放Jpeg图片帧
		(void) FreeJpeg(jpeg_frame);
		return ERR;
	}
	
	logger->debug("Yuv2Jpeg_time: {:.3} s", sw);

	// 用于测试，保存Jpeg图片帧至本地，如果客户将jpeg_frame上传至平台，不需要保存本地,
	// 只需要将jpeg_frame.addr_virt, jpeg_frame.size的内容封装成TLV数据内容，并上报至平台；
	(void) SaveJpeg(jpeg_frame, save_path);

	sw.reset();

	unsigned char *jpeg_ptr =  (unsigned char *)(uintptr_t)jpeg_frame.addr_virt;
	std::vector<unsigned char> buff(jpeg_ptr,jpeg_ptr+jpeg_frame.size);
	JPEGToMat(dst, buff);
	
	logger->debug("JpegToMat_time: {:.3} s", sw);

	// sw.reset();
	// dst = pre_process_Mat(dst);
	// logger->debug("pre_process_Mat_time: {:.3} s", sw);

	sw.reset();

	cv::Mat leboxMat;
	pre_process_letterbox_Mat(dst, forwardsize_w, forwardsize_h, leboxMat);

	logger->debug("pre_process_letterbox_Mat_time: {:.3} s", sw);
	
	sw.reset();

	BGR2yuv_nv21(leboxMat, pyuv);

	logger->debug("BGR2yuv_nv21_time: {:.3} s", sw);

	sw.reset();

	// 释放Jpeg图片帧
	(void) FreeJpeg(jpeg_frame);

	logger->debug("FreeJpeg_time: {:.3} s", sw);

	return HW_OK;
}

int32_t JpegFile::YuvToMat(sdc_yuv_frame_s &yuv_frame, cv::Mat &dst) const
{
	uint32_t w = yuv_frame.width * 2 / 2;
	uint32_t h = yuv_frame.height * 2 / 2;
    if(w != yuv_frame.width || h != yuv_frame.height){
        LOG_ERROR("input resize not proper, should could devide by 2\n");
        return ERR;
    }
    uint32_t imageLength = w * h * 3 / 2;
	cv::Mat input_yuv;
	input_yuv.create((int32_t)h * 3 / 2 , (int32_t)w, CV_8UC1);
	memcpy(input_yuv.data, (char *)(uintptr_t)yuv_frame.addr_virt, imageLength);	
	dst.create(h , w, CV_8UC3);
	cv::cvtColor(input_yuv, dst, cv::COLOR_YUV2BGR_NV21);

	return HW_OK;
}

}